/*
 * @Author: Billy
 * @Date: 2020-12-30 23:54:54
 * @LastEditTime: 2021-01-17 10:32:58
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /bhFun/bhFun.js 
 */

/** 
 * 目录
 *   - 验证身份证: validateIdCard
 *      - 去除掉字符串前后空格: trim
 *      - 检查身份证号码是否符合规范，包括长度，类型: isCardNo
 *      - 检查身份证号码省份 : checkProvince
 *      - 检查生日是否正确 :checkBirthday
 *      - 校验日期: verifyBirthday
 *      - 身份证校验位校验: checkParity
 *      - 15位转18位身份证: changeFivteenToEighteen
 * 
 *   - 验证手机号: ryValidatePhoneNum
 *   - 避免重复点击: throttle
 *   - 避免重复点击并且保证只触发一次: debounce
 *   - 加密字符串: encryptionStr
 *   - 返回日常用的Date对象:    dateFormat
 *   - 生成两数之间的随机数: randomNum
 *   - 多次触发只执行一次 ryThrottle
 *   - accAdd 加法运算
 *   - accSub 减法运算
 *   - accMul 乘法
 *   - accDiv 除法
 */

class bhFun {
    constructor() {
    }

    /**
     * @Date: 2021-01-14 08:06:52
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: cloneObject
     * @Description: 寄生组合式继承,deepCopy(实现深拷贝)和es6 extends一致
     * @Return: 
     * @param {*} parent
     * @param {*} child
     */
    deepCopyObject(parent, child) {
        child.prototype = Object.create(parent.prototype);
        child.prototype.constructor = child;
    }

    /**
     * @Date: 2020-12-31 00:23:59
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: trim
     * @Description: 去除掉字符串前后空格
     * @Return: str
     * @param {*} str
     */
    trim(str) {
        if (typeof str !== 'string') return str;
        return str.replace(/^(\s|\xA0)+|(\s|\xA0)+$/g, '');
    }

    /**
     * @Date: 2020-12-31 00:30:25
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: isCardNo
     * @Description: 检查号码是否符合规范，包括长度，类型
     * @Return: true/false
     * @param {*} idCard
     */
    isCardNo(idCard) {
        let reg = /(^\d{15}$)|(^\d{17}(\d|X|x)$)/;
        if (reg.test(idCard)) return true;

        console.error('号码不符合规范(长度/类型)');
        return false;
    }

    /**
     * @Date: 2020-12-31 00:38:49
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: checkProvince
     * @Description: 
     * @Return: 
     * @param {*} idCard
     * @param {*} vcity
     */
    checkProvince(idCard, vcity) {
        let province = idCard.substr(0, 2);
        if (vcity[province]) return vcity[province];

        console.error('校验省份失败');
        return false;
    }

    /**
     * @Date: 2021-01-01 09:52:54
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: checkBirthday
     * @Description: 获取身份证号(不通过校验时返回false)
     * @Return: Object/Boolean
     * @param {*} idCard
     */
    checkBirthday(idCard) {
        let len = idCard.length;
        let arr_data;
        let year;
        let month;
        let day;
        let birthday;

        //身份证15位时，次序为省（3位）市（3位）年（2位）月（2位）日（2位）校验位（3位），皆为数字
        if (len == '15') {
            let re_fifteen = /^(\d{6})(\d{2})(\d{2})(\d{2})(\d{3})$/;
            arr_data = idCard.match(re_fifteen);
            year = arr_data[2];
            month = arr_data[3];
            day = arr_data[4];
            birthday = new Date('19' + year + '/' + month + '/' + day);
            this.birthday = `${year}-${month}-${day}`;
            return this.verifyBirthday('19' + year, month, day, birthday);
        }
        //身份证18位时，次序为省（3位）市（3位）年（4位）月（2位）日（2位）校验位（4位），校验位末尾可能为X
        if (len == '18') {
            let re_eighteen = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X|x)$/;
            arr_data = idCard.match(re_eighteen);
            year = arr_data[2];
            month = arr_data[3];
            day = arr_data[4];
            birthday = new Date(year + '/' + month + '/' + day);
            this.birthday = `${year}-${month}-${day}`;
            return this.verifyBirthday(year, month, day, birthday);
        }
        console.error('无效身份证');
        return false;
    };

    /**
     * @Date: 2021-01-01 10:05:13
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: verifyBirthday
     * @Description: 
     * @Return: 
     * @param {*} year
     * @param {*} month
     * @param {*} day
     * @param {*} birthday
     */
    verifyBirthday(year, month, day, birthday) {
        let now = new Date();
        let now_year = now.getFullYear();
        //年月日是否合理
        if (birthday.getFullYear() == year && (birthday.getMonth() + 1) == month && birthday.getDate() == day) {
            //判断年份的范围（0岁到100岁之间)
            let time = now_year - year;
            if (time >= 0 && time <= 100) {
                return `${year}-${month}-${day}`;
            }
        }
        console.error('无效日期');
        return false;
    }

    /**
     * @Date: 2021-01-01 10:09:22
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: checkParity
     * @Description: 校验位的检测
     * @Return: Boolean
     * @param {*} idCard
     */
    checkParity(idCard) {
        //15位转18位
        idCard = this.changeFivteenToEighteen(idCard);
        let len = idCard.length;
        if (len == '18') {
            let arrInt = new Array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);
            let arrCh = new Array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
            let cardTemp = 0,
                i, valnum;
            for (i = 0; i < 17; i++) {
                cardTemp += idCard.substr(i, 1) * arrInt[i];
            }
            valnum = arrCh[cardTemp % 11];
            if (valnum == idCard.substr(17, 1).toLocaleUpperCase()) {
                return true;
            }
        }
        return false;
    }

    /**
     * @Date: 2021-01-01 10:11:45
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: changeFivteenToEighteen
     * @Description:  15位转18位身份证号
     * @Return: String
     * @param {*} idCard
     */
    changeFivteenToEighteen(idCard) {
        if (idCard.length == '15') {
            let arrInt = new Array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);
            let arrCh = new Array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
            let cardTemp = 0,
                i;
            idCard = idCard.substr(0, 6) + '19' + idCard.substr(6, idCard.length - 6);
            for (i = 0; i < 17; i++) {
                cardTemp += idCard.substr(i, 1) * arrInt[i];
            }
            idCard += arrCh[cardTemp % 11];
            return idCard;
        }
        return idCard;
    }

    /**
     * @Date: 2021-01-06 19:37:27
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: ryGetCertNoDetail
     * @Description: 根据身份证获取年龄和生日
     * @Return: Object/Boolean
     * @param {*} idCard
     * @param {*} serverTime
     */
    ryGetCertNoDetail(idCard, serverTime) {
        let len = idCard.length;
        let arr_data;
        let year;
        let month;
        let day;
        let birthday;
        let birthdayText;
        let reg;
        let nowYear;
        let age;

        //身份证15位时，次序为省（3位）市（3位）年（2位）月（2位）日（2位）校验位（3位），皆为数字
        if (len === 15) reg = /^(\d{6})(\d{2})(\d{2})(\d{2})(\d{3})$/;

        //身份证18位时，次序为省（3位）市（3位）年（4位）月（2位）日（2位）校验位（4位），校验位末尾可能为X
        if (len === 18) reg = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X|x)$/;

        if (!reg) return false

        arr_data = idCard.match(reg);
        year = arr_data[2];
        month = arr_data[3];
        day = arr_data[4];

        if (len === 15) {
            birthdayText = '19' + year + '-' + month + '-' + day;
            birthday = new Date(birthdayText).getTime();
        } else if (len === 18) {
            birthdayText = year + '-' + month + '-' + day;
            birthday = new Date(birthdayText).getTime();
        }

        nowYear = serverTime ? serverTime : new Date().getFullYear(); // 这里换成服务器时间最好
        age = nowYear - year < 0 ? 0 : nowYear - year;

        return {
            birthday: birthday,
            birthdayText: birthdayText,
            age: age,
            ageText: age ? age : age + '岁',
        };
    }

    /**
     * @Date: 2020-12-31 00:25:38
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: validateIdCard
     * @Description: 验证身份证 (不通过校验时返回false)
     * @Return: true/false 
     * @param {*} idCard
     */
    validateIdCard(idCard) {
        this.vcity = {
            11: "北京", 12: "天津", 13: "河北", 14: "山西", 15: "内蒙古", 21: "辽宁", 22: "吉林", 23: "黑龙江", 31: "上海", 32: "江苏", 33: "浙江", 34: "安徽", 35: "福建", 36: "江西", 37: "山东", 41: "河南", 42: "湖北", 43: "湖南", 44: "广东", 45: "广西", 46: "海南", 50: "重庆", 51: "四川", 52: "贵州", 53: "云南", 54: "西藏", 61: "陕西", 62: "甘肃", 63: "青海", 64: "宁夏", 65: "新疆", 71: "台湾", 81: "香港", 82: "澳门", 91: "国外"
        };

        //  所在的省份
        this.province = null;

        // 出生日期
        this.birthday = null;

        // 是否为空
        if (!this.trim(idCard)) return false;

        // 校验身份证号码是否符合规范，包括长度，类型
        if (!this.isCardNo(idCard)) return false;

        // 校验身份证省份
        if (!this.checkProvince(idCard, this.vcity)) return false;
        this.province = this.checkProvince(idCard, this.vcity)

        // 检查生日是否正确
        if (!this.checkBirthday(idCard)) return false;
        this.birthday = this.checkBirthday(idCard);

        // 检验位的检测
        if (!this.checkParity(idCard)) return false;

        console.log('校验通过');
        return {
            result: true,
            province: this.province,
            birthday: this.birthday
        };
    }

    /**
     * @Date: 2021-01-06 19:24:04
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: ryValidatePhoneNum
     * @Description: 验证手机号
     * @Return: Boolean
     * @param {*} phone
     */
    validatePhoneNum(phone) {
        // 表示以1开头，第二位可能是3/4/5/6/7/8/9等的任意一个，在加上后面的\d表示数字[0-9]的9位，总共加起来11位结束
        let myreg = /^[1][3,4,5.6,7,8,9][0-9]{9}$/;

        if (!phone) return false;

        if (!myreg.test(phone)) return false;

        return true;
    }

    /**
     * @Date: 2021-01-06 19:27:42
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: ryThrottle
     * @Description: 避免重复点击
     * @Return: timer
     */
    throttle(handler, wait = 500) {
        let lastTime = 0;
        return function () {
            let nowTime = new Date().getTime();
            if (nowTime - lastTime > wait) {
                handler.apply(this, arguments);
                lastTime = nowTime;
            }
        }
    }

    /**
    * @Date: 2021-01-06 19:27:42
    * @Author: Billy
    * @LastEditors: Billy
    * @FunName: ryDebounce
    * @Description: 避免重复点击并且保证只触发一次
    * @Return: timer
    */
    debounce(fn, delay = 500) {
        let timer = null;
        return function () {
            //清除已存在的定时器
            timer && clearTimeout(timer);
            timer = setTimeout(fn.apply(this, arguments), delay);
        }
    }

    /**
     * @Date: 2021-01-06 19:35:29
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: ryEncryptionStr
     * @Description: 加密字符串
     * @Return: value
     * @param {*} value
     * @param {*} type
     * @param {*} front
     * @param {*} after
     */
    encryptionStr(value, type, front, after) {
        if (typeof value !== 'string') return console.error('请输入字符串');

        switch (type) {
            case 'idNumber':
                front = 5;
                after = 6;
                break;

            case 'phone':
                front = 3;
                after = 4;
                break;

            default:
                if (typeof front === 'undefined') front = 3;
                if (typeof after === 'undefined') after = 4;
        }

        if (value.length <= front + after) return value

        return value.substring(0, front) + '*'.repeat(value.length - (front + after)) + value.substring(value.length - after)
    }

    /**
     * @Date: 2021-01-06 19:50:09
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: ryDateFormat
     * @Description: 返回日常用的Date对象
     * @Return: Object/Boolean
     * @param {*} timeStamp 需要查询的时间戳(毫秒)
     * @param {*} secondsStatus 是否需要显示秒
     * @param {*} formatOption 格式
     *  symbol: '-'
     */
    dateFormat(timeStamp = new Date().getTime(), secondsStatus, formatOption, serverTime) {
        if (!timeStamp) return false

        let date = new Date(timeStamp);
        let year = date.getFullYear();
        let month = date.getMonth() + 1;
        let day = date.getDate();
        let hours = date.getHours();
        let minutes = date.getMinutes();
        let seconds = date.getSeconds();
        let timestamp = date.getTime();

        let result;
        let resultYear;
        let resultTime;
        let resultYearText;
        let resultMonthText;

        let diff;
        let isToday = false;

        let weekArray = new Array("日", "一", "二", "三", "四", "五", "六");
        let week = weekArray[new Date(date).getDay()];

        if (!formatOption) {
            formatOption = {
                symbol: '-'
            }
        }

        month = month >= 10 ? month : '0' + month;

        day = day >= 10 ? day : '0' + day;
        hours = hours >= 10 ? hours : '0' + hours;
        minutes = minutes >= 10 ? minutes : '0' + minutes;
        seconds = seconds >= 10 ? seconds : '0' + seconds;
        resultYear = year + formatOption.symbol + month + formatOption.symbol + day;
        resultYearText = year + '年' + month + '月' + day + '日';
        resultMonthText = month + '月' + day + '日';
        if (secondsStatus) {
            resultTime = hours + ':' + minutes + ':' + seconds;
        } else {
            resultTime = hours + ':' + minutes;
        }

        result = resultYear + ' ' + resultTime;

        diff = serverTime ? serverTime - timeStamp : new Date().getTime() - timeStamp;

        if (diff >= 0 && diff < 86400000) isToday = true;

        return {
            year: year,
            month: month,
            day: day,
            hours: hours,
            minutes: minutes,
            seconds: seconds,
            result: result,
            resultYear: resultYear,
            resultYearText: resultYearText,
            resultMonthText: resultMonthText,
            resultTime: resultTime,
            timestamp: timestamp,
            week: '周' + week,
            isToday: isToday
        }
    }

    /**
     * @Date: 2021-01-06 20:09:28
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: randomNum
     * @Description: 生成两数之间的随机数
     * @Return: Number
     * @param {*} min
     * @param {*} max
     */
    randomNum(min, max) {
        return Math.floor(min + Math.random() * (max - min));
    }

    /**
     * @Date: 2021-01-11 20:25:04
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: ryThrottle2
     * @Description: 只触发最后一次
     * @Return: function
     * @param {*} handler
     * @param {*} myTimer 自己页面的全局变量 必须要传
     * @param {*} wait
     */
    ryThrottle(handler, myTimer, wait) {
        if (!wait) wait = 500;

        if (myTimer) clearTimeout(myTimer);

        return function () {
            setTimeout(function () {
                handler.apply(this, arguments)
            }, wait)
        };
    }

    /**
     * @Date: 2021-01-11 20:54:01
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: accAdd
     * @Description: 返回较为精确的加法结果
     * @Return: number
     * @param {*} arg1
     * @param {*} arg2
     */
    accAdd(arg1, arg2) {
        let r1, r2, m, c;
        try {
            r1 = arg1.toString().split(".")[1].length;
        } catch (e) {
            r1 = 0;
        }
        try {
            r2 = arg2.toString().split(".")[1].length;
        } catch (e) {
            r2 = 0;
        }

        c = Math.abs(r1 - r2);
        m = Math.pow(10, Math.max(r1, r2));
        if (c > 0) {
            var cm = Math.pow(10, c);
            if (r1 > r2) {
                arg1 = Number(arg1.toString().replace(".", ""));
                arg2 = Number(arg2.toString().replace(".", "")) * cm;
            } else {
                arg1 = Number(arg1.toString().replace(".", "")) * cm;
                arg2 = Number(arg2.toString().replace(".", ""));
            }
        } else {
            arg1 = Number(arg1.toString().replace(".", ""));
            arg2 = Number(arg2.toString().replace(".", ""));
        }
        return (arg1 + arg2) / m;
    }

    /**
     * @Date: 2021-01-11 20:55:02
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: accSub
     * @Description: 返回较为精确的减法结果
     * @Return: number
     * @param {*} arg1
     * @param {*} arg2
     */
    accSub(arg1, arg2) {
        let r1, r2, m, n;
        try {
            r1 = arg1.toString().split(".")[1].length;
        } catch (e) {
            r1 = 0;
        }
        try {
            r2 = arg2.toString().split(".")[1].length;
        } catch (e) {
            r2 = 0;
        }
        m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //动态控制精度长度
        n = (r1 >= r2) ? r1 : r2;
        return ((arg1 * m - arg2 * m) / m).toFixed(n);
    }

    /**
     * @Date: 2021-01-11 20:57:57
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: accMul
     * @Description: 返回较为精确的乘法结果
     * @Return: number
     * @param {*} arg1
     * @param {*} arg2
     */
    accMul(arg1, arg2) {
        let m = 0,
            s1 = arg1.toString(),
            s2 = arg2.toString();
        try {
            m += s1.split(".")[1].length;
        } catch (e) { }
        try {
            m += s2.split(".")[1].length;
        } catch (e) { }
        return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
    }

    /**
     * @Date: 2021-01-11 20:59:11
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: accDiv
     * @Description: 返回较为精确的除法结果
     * @Return: number
     * @param {*} arg1
     * @param {*} arg2
     */
    accDiv(arg1, arg2) {
        let t1 = 0,
            t2 = 0,
            r1, r2;
        try {
            t1 = arg1.toString().split(".")[1].length;
        } catch (e) { }
        try {
            t2 = arg2.toString().split(".")[1].length;
        } catch (e) { }

        // with (Math) {
        r1 = Number(arg1.toString().replace(".", ""));
        r2 = Number(arg2.toString().replace(".", ""));
        return (r1 / r2) * pow(10, t2 - t1);
        // }
    }

    /**
     * @Date: 2021-01-11 21:14:36
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: forOf
     * @Description: 循环
     * @Return: 
     * @param {*} arr
     * @param {*} fun
     */
    forOf(arr, fun) {
        let item;
        for (let i = 0; i < arr.length; i++) {
            item = arr[i];
            fun(item, i);
        }
    }

    queryUrlParameters(url) {
        let query = url ? url : window.location.search.substring(1);
        let vars = query.split("&");
        let pair;
        let obj = {};
        this.forOf(vars, function (item, index) {
            pair = item.split("=");
            obj[pair[0]] = pair[1];
        });

        return obj;
    }

    /**
     * @Date: 2021-01-17 10:31:23
     * @Author: Billy
     * @LastEditors: Billy
     * @FunName: deepClone
     * @Description: 深拷贝 (可拷贝不可枚举,函数等浅拷贝不能拷贝的对象)
     *  hash 可使用Map 变为强引用
     * @Return: Object
     * @param {*} obj
     * @param {*} hash
     */
    deepClone(obj, hash = new WeakMap()) {
        let isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
        if (obj.constructor === Date) return new Date(obj)       // 日期对象直接返回一个新的日期对象
        if (obj.constructor === RegExp) return new RegExp(obj)     //正则对象直接返回一个新的正则对象

        //如果循环引用了就用 Map 来解决
        if (hash.has(obj)) return hash.get(obj)

        let allDesc = Object.getOwnPropertyDescriptors(obj)

        //遍历传入参数所有键的特性
        let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

        //继承原型链
        hash.set(obj, cloneObj)

        for (let key of Reflect.ownKeys(obj)) {
            cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone2(obj[key], hash) : obj[key]
        }
        return cloneObj
    }
}

